#!/usr/bin/env python3
"""
UC System 端口配置验证工具
检查系统中所有模块的端口配置是否一致且无冲突
"""

import os
import sys
import re
import json
import subprocess
from pathlib import Path
from typing import Dict, List, Tuple, Optional

class PortConfigurationChecker:
    def __init__(self, project_root: str = None):
        self.project_root = Path(project_root) if project_root else Path(__file__).parent
        self.expected_ports = {
            'frontend': 8101,
            'backend': 4000,
            'ai_service': 5001,
            'database': 3306
        }
        self.config_files = {
            'frontend': [
                'endo_sight_uc_web/vite.config.ts',
                'endo_sight_uc_web/package.json'
            ],
            'backend': [
                'endo_sight_uc_server/src/index.js',
                'endo_sight_uc_server/package.json',
                'endo_sight_uc_server/.env'
            ],
            'ai_service': [
                'endo_sight_uc_ai/inference_server.py',
                'endo_sight_uc_ai/.env',
                'endo_sight_uc_ai/.env.example'
            ],
            'database': [
                'endo_sight_uc_server/src/config/database.js'
                # 注意：数据库端口从后端的.env文件中特殊提取
            ]
        }
        self.issues = []
        self.warnings = []

    def log_issue(self, severity: str, service: str, file_path: str, message: str):
        """记录问题"""
        issue = {
            'severity': severity,
            'service': service,
            'file': file_path,
            'message': message
        }
        if severity == 'ERROR':
            self.issues.append(issue)
        else:
            self.warnings.append(issue)

    def extract_port_from_file(self, file_path: Path) -> List[int]:
        """从文件中提取端口号"""
        ports = []

        try:
            content = file_path.read_text(encoding='utf-8')
            filename = file_path.name.lower()

            # 根据文件类型使用不同的匹配策略
            if 'vite.config.ts' in filename or 'vite.config.js' in filename:
                # Vite配置文件 - 只提取server.port，忽略proxy中的target端口
                server_port_match = re.search(r'server:\s*{[^}]*port:\s*(\d+)', content, re.DOTALL)
                if server_port_match:
                    ports.append(int(server_port_match.group(1)))

            elif 'inference_server.py' in filename:
                # AI服务配置 - 只提取服务PORT，忽略ALLOWED_ORIGINS中的端口
                port_matches = re.findall(r'^PORT\s*=\s*(\d+)', content, re.MULTILINE)
                for match in port_matches:
                    ports.append(int(match))

            elif '.env' in filename:
                # 环境变量文件 - 只提取对应服务的端口
                if 'server' in str(file_path):
                    # 后端服务的.env文件 - 只提取后端服务端口(PORT)
                    port_match = re.search(r'^PORT\s*=\s*(\d+)', content, re.MULTILINE)
                    if port_match:
                        ports.append(int(port_match.group(1)))
                    # 注意：DB_PORT属于数据库服务，不在后端服务中提取
                else:
                    # AI服务的.env文件 - 只提取AI服务端口(PORT)
                    port_match = re.search(r'^PORT\s*=\s*(\d+)', content, re.MULTILINE)
                    if port_match:
                        ports.append(int(port_match.group(1)))

            elif 'index.js' in filename and 'server' in str(file_path):
                # Node.js服务器 - 只提取listen的端口，忽略CORS配置
                listen_match = re.search(r'listen\s*\(\s*process\.env\.PORT\s*\|\|\s*(\d+)', content)
                if listen_match:
                    ports.append(int(listen_match.group(1)))
                else:
                    # 备用匹配
                    listen_match = re.search(r'listen\s*\(\s*(\d+)', content)
                    if listen_match:
                        ports.append(int(listen_match.group(1)))

            elif 'database.js' in filename:
                # 数据库配置 - 只提取DB_PORT
                db_port_match = re.search(r'DB_PORT\s*[=:]\s*(\d+)', content)
                if db_port_match:
                    ports.append(int(db_port_match.group(1)))

            else:
                # 通用匹配 - 更精确的模式
                patterns = [
                    r'^PORT\s*=\s*(\d+)',  # 环境变量
                    r'^DB_PORT\s*=\s*(\d+)',  # 数据库端口环境变量
                    r'port:\s*(\d+)',  # 配置对象中的端口
                    r'port\s*=\s*(\d+)',  # Python赋值
                    r'"port":\s*"?(\d+)"?',  # JSON配置
                    r'listen\s*\(\s*(\d+)',  # listen函数
                ]

                for pattern in patterns:
                    matches = re.findall(pattern, content, re.MULTILINE | re.IGNORECASE)
                    for match in matches:
                        port = int(match)
                        if 1024 <= port <= 65535:  # 有效端口范围
                            ports.append(port)

        except Exception as e:
            self.log_issue('WARNING', 'general', str(file_path), f"读取文件失败: {str(e)}")

        return list(set(ports))  # 去重

    def check_service_ports(self, service_name: str) -> Dict[str, any]:
        """检查特定服务的端口配置"""
        result = {
            'service': service_name,
            'expected_port': self.expected_ports.get(service_name),
            'found_ports': [],
            'config_files': {},
            'is_consistent': True
        }

        config_files = self.config_files.get(service_name, [])

        for config_file in config_files:
            file_path = self.project_root / config_file
            if file_path.exists():
                ports = self.extract_port_from_file(file_path)
                result['found_ports'].extend(ports)
                result['config_files'][str(file_path)] = ports

                # 检查端口一致性
                if ports:
                    for port in ports:
                        if port != result['expected_port']:
                            result['is_consistent'] = False
                            self.log_issue(
                                'ERROR' if port in self.expected_ports.values() and port != result['expected_port'] else 'WARNING',
                                service_name,
                                str(file_path),
                                f"发现端口 {port}，预期端口 {result['expected_port']}"
                            )
            else:
                self.log_issue('WARNING', service_name, str(file_path), "配置文件不存在")

        # 特殊处理：数据库服务需要从后端的.env文件中提取DB_PORT
        if service_name == 'database':
            env_file = self.project_root / 'endo_sight_uc_server/.env'
            if env_file.exists():
                try:
                    content = env_file.read_text(encoding='utf-8')
                    db_port_match = re.search(r'^DB_PORT\s*=\s*(\d+)', content, re.MULTILINE)
                    if db_port_match:
                        db_port = int(db_port_match.group(1))
                        result['found_ports'].append(db_port)
                        result['config_files'][str(env_file)] = [db_port]

                        # 检查端口一致性
                        if db_port != result['expected_port']:
                            result['is_consistent'] = False
                            self.log_issue(
                                'ERROR',
                                service_name,
                                str(env_file),
                                f"发现端口 {db_port}，预期端口 {result['expected_port']}"
                            )
                except Exception as e:
                    self.log_issue('WARNING', service_name, str(env_file), f"读取.env文件失败: {str(e)}")

        result['found_ports'] = list(set(result['found_ports']))
        return result

    def check_port_conflicts(self) -> List[Dict]:
        """检查端口冲突"""
        port_usage = {}
        conflicts = []

        for service_name, expected_port in self.expected_ports.items():
            if expected_port not in port_usage:
                port_usage[expected_port] = []
            port_usage[expected_port].append(service_name)

        for port, services in port_usage.items():
            if len(services) > 1:
                conflicts.append({
                    'port': port,
                    'conflicting_services': services,
                    'message': f"端口 {port} 被多个服务使用: {', '.join(services)}"
                })

        return conflicts

    def check_system_port_usage(self) -> Dict[int, str]:
        """检查系统中实际占用的端口"""
        port_usage = {}

        try:
            # 使用netstat检查端口占用
            if os.name == 'nt':  # Windows
                result = subprocess.run(['netstat', '-ano'], capture_output=True, text=True)
            else:  # Linux/macOS
                result = subprocess.run(['netstat', '-tulpn'], capture_output=True, text=True)

            if result.returncode == 0:
                lines = result.stdout.split('\n')
                for line in lines:
                    match = re.search(r':(\d{4,5})\s+.*?(\d+)$', line)
                    if match:
                        port = int(match.group(1))
                        pid = match.group(2)
                        port_usage[port] = pid

        except Exception as e:
            self.log_issue('WARNING', 'system', 'netstat', f"检查系统端口占用失败: {str(e)}")

        return port_usage

    def generate_report(self) -> Dict:
        """生成完整的检查报告"""
        report = {
            'timestamp': str(Path().cwd()),
            'project_root': str(self.project_root),
            'services': {},
            'port_conflicts': [],
            'system_port_usage': {},
            'issues': self.issues,
            'warnings': self.warnings,
            'summary': {
                'total_issues': len(self.issues),
                'total_warnings': len(self.warnings),
                'services_checked': 0,
                'ports_consistent': True
            }
        }

        # 检查各个服务
        for service_name in self.expected_ports.keys():
            service_result = self.check_service_ports(service_name)
            report['services'][service_name] = service_result
            report['summary']['services_checked'] += 1

            if not service_result['is_consistent']:
                report['summary']['ports_consistent'] = False

        # 检查端口冲突
        conflicts = self.check_port_conflicts()
        report['port_conflicts'] = conflicts
        if conflicts:
            report['summary']['ports_consistent'] = False

        # 检查系统端口占用
        system_usage = self.check_system_port_usage()
        report['system_port_usage'] = system_usage

        return report

    def print_report(self, report: Dict):
        """打印检查报告"""
        print("=" * 80)
        print("UC System 端口配置检查报告")
        print("=" * 80)
        print(f"项目路径: {report['project_root']}")
        print(f"检查时间: {report['timestamp']}")
        print()

        # 服务配置摘要
        print("服务端口配置摘要:")
        print("-" * 50)
        for service_name, config in report['services'].items():
            status = "[OK]" if config['is_consistent'] else "[ERROR]"
            expected = config['expected_port']
            found = config['found_ports'] if config['found_ports'] else ["未找到"]
            print(f"{status} {service_name:15} | 预期: {expected:5} | 实际: {found}")
        print()

        # 端口冲突
        if report['port_conflicts']:
            print("端口冲突:")
            print("-" * 50)
            for conflict in report['port_conflicts']:
                print(f"[ERROR] 端口 {conflict['port']}: {conflict['message']}")
            print()

        # 系统端口占用
        print("系统端口占用状态:")
        print("-" * 50)
        for service_name, expected_port in self.expected_ports.items():
            if expected_port in report['system_port_usage']:
                pid = report['system_port_usage'][expected_port]
                print(f"[WARNING] 端口 {expected_port} ({service_name}) 被进程 {pid} 占用")
            else:
                print(f"[OK] 端口 {expected_port} ({service_name}) 可用")
        print()

        # 问题和警告
        if report['issues']:
            print("错误:")
            print("-" * 50)
            for issue in report['issues']:
                print(f"[ERROR] {issue['service']}.{Path(issue['file']).name}: {issue['message']}")
            print()

        if report['warnings']:
            print("警告:")
            print("-" * 50)
            for warning in report['warnings']:
                print(f"[WARNING] {warning['service']}.{Path(warning['file']).name}: {warning['message']}")
            print()

        # 总结
        print("检查总结:")
        print("-" * 50)
        print(f"检查服务数: {report['summary']['services_checked']}")
        print(f"错误数量: {report['summary']['total_issues']}")
        print(f"警告数量: {report['summary']['total_warnings']}")
        status_text = "通过" if report['summary']['ports_consistent'] else "失败"
        print(f"[{'OK' if report['summary']['ports_consistent'] else 'ERROR'}] 端口配置一致性: {status_text}")

        if report['summary']['total_issues'] == 0 and report['summary']['ports_consistent']:
            print("\n[SUCCESS] 所有端口配置检查通过!")
        else:
            print("\n[WARNING] 发现配置问题，请根据上述报告进行调整。")

    def save_report(self, report: Dict, output_file: str = None):
        """保存报告到文件"""
        if not output_file:
            from datetime import datetime
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            output_file = f"port_configuration_report_{timestamp}.json"

        output_path = self.project_root / output_file
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(report, f, indent=2, ensure_ascii=False)

        print(f"\n[INFO] 详细报告已保存到: {output_path}")

def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(description='UC System 端口配置检查工具')
    parser.add_argument('--project-root', help='项目根目录路径')
    parser.add_argument('--output', help='输出报告文件名')
    parser.add_argument('--quiet', action='store_true', help='静默模式，只输出错误')

    args = parser.parse_args()

    checker = PortConfigurationChecker(args.project_root)
    report = checker.generate_report()

    if not args.quiet:
        checker.print_report(report)

    if args.output:
        checker.save_report(report, args.output)
    elif report['summary']['total_issues'] > 0 or not report['summary']['ports_consistent']:
        checker.save_report(report)

    # 返回适当的退出码
    exit_code = 0 if report['summary']['total_issues'] == 0 and report['summary']['ports_consistent'] else 1
    sys.exit(exit_code)

if __name__ == '__main__':
    main()